﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using MentalAlchemy.Atomics;

namespace MentalAlchemy.Molecules
{
	#region - Types. -
	/// <summary>
	/// Class to hold settings for a particular script syntaxis.
	/// </summary>
	[Serializable]
	public class ScriptSyntaxis
	{
		public string[] Keywords;
		public string[] Operators;
		public string[] OpeningBrackets;
		public string[] ClosingBrackets;
		public string[] ExpressionEnd;
		public string[] Punctuation;

		public string ScriptKeyword;
		public string IfKeyword;
		public string LoopKeyword;
		public string IndexingBracket;
		public string AssignmentSymbol;

		public BNFExpression HeaderBNF;
		public BNFExpression ExpressionBNF;
		public BNFExpression OperationBNF;
		public List<BNFExpression> bnfs;

		public BNFExpression GetBNFByName (string name)
		{
			if (bnfs == null) return null;

			foreach (var bnf in bnfs)
			{
				if (string.Compare(bnf.Name, name, true) == 0) return bnf;
				if (string.Compare(bnf.Alias, name, true) == 0) return bnf;
			}
			return null;
		}

		public static List<BNFExpression> GetDefaultBNFs ()
		{
			var res = new List<BNFExpression>();
			res.Add(BNFExpression.Header());
			res.Add(BNFExpression.ArithmeticOperation());
			res.Add(BNFExpression.Assign());
			res.Add(BNFExpression.Condition());
			res.Add(BNFExpression.ConditionalExpression());
			res.Add(BNFExpression.Exit());
			res.Add(BNFExpression.Expression());
			res.Add(BNFExpression.ExpressionTerm());
			res.Add(BNFExpression.FunctionCall());
			res.Add(BNFExpression.ListExpression());
			res.Add(BNFExpression.ListItemExpression());
			res.Add(BNFExpression.LogicalOperation());
			res.Add(BNFExpression.LoopExpression());
			res.Add(BNFExpression.Operation());
			res.Add(BNFExpression.Sequence());
			res.Add(BNFExpression.SequenceItem());
			res.Add(BNFExpression.ScriptTree());
			return res;
		}
	}

	/// <summary>
	/// Enumeration to define type of the token.
	/// </summary>
	public enum TokenType
	{
		Unknown,
		Identifier,
		Operator,
		Keyword,
		OpeningBracket,
		ClosingBracket,
		Punctuation,
		BNFConstruct,
		ExpressionEnd
	};

	/// <summary>
	/// Class to represents tokens.
	/// </summary>
	[Serializable]
	public class Token
	{
		public string Text;
		public TokenType TokenType;
		public int BnfLen;	// defines number of tokens, expressing a BNF, which starts from this token.
		public string BnfName;	// defines name of a BNF, which starts from this token.
		public object Tag;

		#region - Constructors. -
		public Token() { }

		public Token(TokenType type, string text)
		{
			TokenType = type;
			Text = text;
		} 
		#endregion

		#region - Public methods. -
		public new string ToString()
		{
			var prefix = TokenType + ": ";
			return prefix + Text;
		}

		public bool Equals(Token token)
		{
			if (TokenType == TokenType.Identifier)
			{
				return TokenType == token.TokenType;
			}
			var textcomp = (string.Compare(Text, token.Text, true, CultureInfo.CurrentCulture) == 0);
			return (TokenType == token.TokenType) && textcomp;
		} 
		#endregion
	} 
	#endregion

	public class Scripting
	{
		public const string TOKEN_SEPARATORS = " \t\n(){}[]\"\'.,;:-+*/%\\=";
		public const string TOKEN_SEPARATORS_NOSPACE = "(){}[]\"\'.,;:-+*/%\\=";

		#region - Tokenization. -
		/// <summary>
		/// [molecule]
		/// 
		/// Performs tokenization of the given string using specified set of keywords.
		/// </summary>
		/// <param name="text"></param>
		/// <param name="synt"></param>
		/// <returns></returns>
		public static Token[] Tokenize(string text, ScriptSyntaxis synt)
		{
			var res = new List<Token>();
			var spaces = new[] { ' ', '\t', '\n' };

			//var parts = text.Split(spaces);
			var parts = ScriptingElements.Split(text, spaces);
			var seps = TOKEN_SEPARATORS_NOSPACE.ToArray();
			foreach (var part in parts)
			{
				if (IsAtomic(part))
				{
					var temp = new Token();
					temp.Text = part;
					res.Add(temp);
				}
				else
				{
					var tokens = Strings.SplitWithSeparators(part, seps);
					foreach (var token in tokens)
					{
						var temp = new Token();
						temp.Text = token;
						res.Add(temp);
					}
				}
			}


			// define type of each token.
			AssignTokenTypes(res, synt);

			// perform correction of the resulting tokens.
			RemoveEmptyIdentifiers(res);
			RemoveDoubleOperators(res);
			return res.ToArray();
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Assigns types to the given tokens according to the script syntaxis.
		/// Note that token types are non-overlaping, i.e. token can't be of two types simultaneously.
		/// </summary>
		/// <param name="tokens"></param>
		/// <param name="synt"></param>
		public static void AssignTokenTypes(List<Token> tokens, ScriptSyntaxis synt)
		{
			var bracketLevel = 0;
			foreach (var token in tokens)
			{
				if (synt.Keywords != null && Strings.Contains(synt.Keywords, token.Text, true))
				{
					token.TokenType = TokenType.Keyword;
					continue;
				}
				if (synt.Operators != null && Strings.Contains(synt.Operators, token.Text, true))
				{
					token.TokenType = TokenType.Operator;
					continue;
				}
				if (synt.OpeningBrackets != null && Strings.Contains(synt.OpeningBrackets, token.Text, true))
				{
					token.TokenType = TokenType.OpeningBracket;
					token.Tag = bracketLevel;
					bracketLevel++;
					continue;
				}
				if (synt.ClosingBrackets != null && Strings.Contains(synt.ClosingBrackets, token.Text, true))
				{
					token.TokenType = TokenType.ClosingBracket;
					bracketLevel--;
					token.Tag = bracketLevel;
					continue;
				}
				if (synt.ExpressionEnd != null && Strings.Contains(synt.ExpressionEnd, token.Text, true))
				{
					token.TokenType = TokenType.ExpressionEnd;
					continue;
				}
				if (synt.Punctuation != null && Strings.Contains(synt.Punctuation, token.Text, true))
				{
					token.TokenType = TokenType.Punctuation;
					continue;
				}

				token.TokenType = TokenType.Identifier;
			}
		}

		/// <summary>
		/// Removes all identifier-tokens which are either empty or contain quots '"'.
		/// </summary>
		/// <param name="tokens"></param>
		public static void RemoveEmptyIdentifiers(List<Token> tokens)
		{
			for (int i = 0; i < tokens.Count; i++)
			{
				if (tokens[i].TokenType == TokenType.Identifier)
				{
					if (tokens[i].Text == "")
					{
						tokens.RemoveAt(i);
						i--;	// step back.
					}
					else if (tokens[i].Text == "\"")
					{
						tokens.RemoveAt(i);

						//
						// perform merging.
						var begin = i;

						// 0. look for closing quotes.
						var mergeStr = "";
						int end = i;
						for (; end < tokens.Count && tokens[end].Text != "\""; ++end)
						{
							mergeStr += tokens[end].Text;
						}

						// 1. remove merged tokens except of the 1st one.
						tokens.RemoveRange(begin + 1, end - begin);
						tokens[begin].Text = mergeStr;
					}
				}
			}
		}

		/// <summary>
		/// Merges two consequent operators into one (required to process <=, >=, == etc.).
		/// </summary>
		/// <param name="tokens"></param>
		public static void RemoveDoubleOperators(List<Token> tokens)
		{
			for (int i = 1; i < tokens.Count; i++)
			{
				if (tokens[i-1].TokenType == TokenType.Operator
					&& tokens[i].TokenType == TokenType.Operator)
				{
					var op2 = tokens[i].Text;
					tokens[i-1].Text += op2;
					tokens.RemoveAt(i);
					i--;	// step back.
				}
			}
		} 
		#endregion

		/// <summary>
		/// [molecule]
		/// 
		/// Creates parse tree using a given set of tokens.
		/// Note: It is assumed that grammar has C-like syntaxis with *obligatory*
		///	Note: curly brackets for loops and conditional statements.
		/// </summary>
		/// <param name="tokens"></param>
		/// <param name="synt"></param>
		/// <returns></returns>
		public static ParseTree CreateParseTree(Token[] tokens, ScriptSyntaxis synt)
		{
			//
			// 0. Script name is expected at the very beginning of tokens array.
			var treeBnf = synt.GetBNFByName(BNFExpression.DEFAULT_TREE_BNF_NAME);
			int count = 0;
			var root = treeBnf.CreateParseTreeNode(new List<Token>(tokens), synt, out count);
			var tree = new ParseTree();
			tree.Nodes = new List<ParseTreeNode>();
			tree.Nodes.Add(root);
			tree.Syntaxis = synt;

			// set name.
			var headerNode = FindChildNode(tree.Nodes[0], BNFExpression.DEFAULT_HEADER_BNF_NAME);
			tree.Name = headerNode.ChildNodes[0].Contents;

			// process arithmetic and conditional expressions and transform subtrees to the inverse Polish form.
			ConvertToPolishNotation(tree);

			return tree;
		}

		#region - Search. -
		/// <summary>
		/// [molecule]
		/// 
		/// Search the tree for the node expressing prescribed BNF.
		/// </summary>
		/// <param name="node"></param>
		/// <param name="bnfName"></param>
		/// <returns></returns>
		public static ParseTreeNode FindChildNode (ParseTreeNode node, string bnfName)
		{
			if (node.IsBnfExpression(bnfName)) return node;

			foreach (var childNode in node.ChildNodes)
			{
				var tempNode = FindChildNode(childNode, bnfName);
				if (tempNode != null)
				{
					return tempNode;
				}
			}
			return null;
		}
		#endregion

		#region - Tree postprocessing. -
		public static ParseTree ConvertToPolishNotation (ParseTree tree)
		{
			//
			// go through the tree and locate 'Expression' nodes.

			// arithmetics.
			// Find all (high-level) subtrees, which start from the 'Expression' node.
			var exprNodes = ScriptingElements.FindNodes(BNFExpression.DEFAULT_EXPRESSION_BNF_NAME, tree.Nodes[0]);
			for (int i = 0; i < exprNodes.Count; i++)
			{
				var node = exprNodes[i];
				var parent = node.Parent;
				var idx = parent.ChildNodes.FindIndex(node.Equals);
				node = ConvertToPolishNotation(node);

				// perform substitution of the node.
				parent.ChildNodes[idx] = node;
			}

			// conditionals.
			// Find all (high-level) subtrees, which start from the 'CondExpression' node.
			exprNodes = ScriptingElements.FindNodes(BNFExpression.DEFAULT_CONDITION_EXPR_BNF_NAME, tree.Nodes[0]);
			for (int i = 0; i < exprNodes.Count; i++)
			{
				var node = exprNodes[i];
				var parent = node.Parent;
				var idx = parent.ChildNodes.FindIndex(node.Equals);
				node = ConvertToPolishNotation(node);

				// perform substitution of the node.
				parent.ChildNodes[idx] = node;
			}

			return tree;
		}

		public static ParseTreeNode TryConvertToPolishNotation(ParseTreeNode node)
		{
			//
			// go through the tree and locate 'Expression' nodes.
			if (node.IsBnfExpression(BNFExpression.DEFAULT_EXPRESSION_BNF_NAME))
			{
				node = ConvertToPolishNotation(node);
			}
			else
			{
				for (int i = 0; i < node.ChildNodes.Count; i++)
				{
					var childNode = node.ChildNodes[i];
					node.ChildNodes[i] = TryConvertToPolishNotation(childNode);
				}
			}

			return node;
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Performs linearization of the node containing mathematical or logical expression.
		/// </summary>
		/// <param name="node"></param>
		/// <returns></returns>
		public static List<ParseTreeNode> Linearize(ParseTreeNode node)
		{
			var tokens = new List<ParseTreeNode>();
			if (node.ChildNodes.Count == 0)
			{
				tokens.Add(node);
				return tokens;
			}

			var openBracket = new ParseTreeNode();
			openBracket.Contents = "(";
			var closeBracket = new ParseTreeNode();
			closeBracket.Contents = ")";

			if (node.Contents == BNFExpression.DEFAULT_EXPRESSION_BNF_NAME)
			{
				tokens.Add(openBracket);
			}
			foreach (var childNode in node.ChildNodes)
			{
				if (childNode.ChildNodes.Count == 0)
				{
					tokens.Add(childNode);
				}
				else if (childNode.Contents == BNFExpression.DEFAULT_ARITHMOP_BNF_NAME
					|| childNode.Contents == BNFExpression.DEFAULT_LOGICOP_BNF_NAME)
				{
					tokens.Add(childNode.ChildNodes[0]);
				}
				else
				{
					tokens.AddRange(Linearize(childNode));
				}
			}
			if (node.Contents == BNFExpression.DEFAULT_EXPRESSION_BNF_NAME)
			{
				tokens.Add(closeBracket);
			}
			return tokens;
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Convert given expression node to the Polish notation.
		/// </summary>
		/// <param name="node"></param>
		/// <returns></returns>
		public static ParseTreeNode ConvertToPolishNotation(ParseTreeNode node)
		{
			// todo: check this method fo arithmetic and logical expressions.
			#region - Operations priorities. -
			var priority = new Dictionary<string, int>();
			// logical operators.
			priority.Add("(", 1);
			priority.Add(")", 1);
			priority.Add("or", 1);
			priority.Add("and", 1);
			priority.Add("OR", 1);
			priority.Add("AND", 1);
			priority.Add("==", 2);
			priority.Add("!=", 2);
			priority.Add(">", 2);
			priority.Add("<", 2);
			priority.Add(">=", 2);
			priority.Add("<=", 2);
			priority.Add("!", 3);
			// arithmetic operators.
			priority.Add("+", 2);
			priority.Add("-", 2);
			priority.Add("*", 3);
			priority.Add("/", 3);
			priority.Add("^", 4);
			#endregion

			var linNodes = Linearize(node);
			var opStack = new Stack<ParseTreeNode>();	// stack for operations.
			var resQueue = new Queue<ParseTreeNode>();	// resulting stack.

			foreach (var linNode in linNodes)
			{
				if (linNode.Token != null && linNode.Token.TokenType == TokenType.Operator)
				{	// put operation into the opStack.
					var curPr = priority[linNode.Token.Text];
					while (opStack.Count > 0 && opStack.Peek().Contents != "(")
					{	// pop-out operations with lower priority.
						var topPr = priority[opStack.Peek().Contents];
						if (topPr >= curPr)
						{
							resQueue.Enqueue(opStack.Pop());
						}
						else
						{
							break;
						}
					}
					opStack.Push(linNode);
				}
				else if (linNode.Contents == "(")
				{	// opening bracket.
					opStack.Push(linNode);
				}
				else if (linNode.Contents == ")")
				{	// closing bracket.
					while (opStack.Count > 0 && opStack.Peek().Contents != "(")
					{
						var top = opStack.Pop();
						resQueue.Enqueue(top);
					}
					opStack.Pop();	// remove opening bracket.
				}
				else
				{	// ok, it's an operand.
					resQueue.Enqueue(linNode);
				}
			}
			while (opStack.Count > 0)
			{	// copy unused contents of [opStack] into the [resStack].
				var tempOp = opStack.Pop();
				resQueue.Enqueue(tempOp);
			}

			var resNode = new ParseTreeNode();
			resNode.Contents = BNFExpression.DEFAULT_EXPRESSION_BNF_NAME;
			resNode.ChildNodes = new List<ParseTreeNode>(resQueue.ToArray());
			resNode.Token = node.Token;
			resNode.Bnf = node.Bnf;

			return resNode;

			//
			// go through the tree and locate 'Expression' nodes.
			//var res = node.Clone();
			//res.ChildNodes.Clear();
			//if (node.ChildNodes.Count == 3)	// two operands and an operation.
			//{
			//    res.Contents = node.ChildNodes[1].Token.Text;
			//    // left child node.
			//    res.ChildNodes.Add(ConvertToPolishNotation(node.ChildNodes[0]));
			//    // right child node.
			//    res.ChildNodes.Add(ConvertToPolishNotation(node.ChildNodes[1]));
			//}
			//else if (node.ChildNodes.Count == 2)	// operator and operand.
			//{
			//    res.Contents = node.ChildNodes[0].Token.Text;
			//    // child node.
			//    res.ChildNodes.Add(ConvertToPolishNotation(node.ChildNodes[1]));
			//}
			//else
			//{	// only an operand.
			//    // copy the node as it is.
			//    res.ChildNodes = new List<ParseTreeNode>(node.ChildNodes);
			//}

			//return node;
		}
		#endregion

		/// <summary>
		/// [molecule]
		/// 
		/// Defines whether given string represents an indivisible entry or
		/// can be split into tokens. It is considered atomic if it contains
		/// none of the following symbols:
		/// - spaces.
		/// - tabs.
		/// - brackets and quotations.
		/// - punctuation (with exception for numbers).
		/// - mathematical operations (with exception for numbers).
		/// </summary>
		/// <param name="str">Input string.</param>
		/// <returns></returns>
		public static bool IsAtomic (string str)
		{
			var idx = str.IndexOfAny(TOKEN_SEPARATORS.ToArray());
			if (idx >= 0 )	// if there's a separator.
			{	
				if (idx < str.Length - 1)
				{	// check if [str] contains a number only if idx is not pointing to the last element in string.
					double temp;
					if (double.TryParse(str, out temp))
					{
						return true;
					}
				}
				return false;
			}
			return true;
		}
	}

	public class ScriptingElements
	{
		#region - Conversion. -
		/// <summary>
		/// [molecule]
		/// 
		/// Converts given array of tokens into array of strings.
		/// </summary>
		/// <param name="tokens">Source array of tokens.</param>
		/// <returns>Array of strings.</returns>
		public static string[] ConvertToStrings(Token[] tokens)
		{
			var res = new string[tokens.Length];
			for (int i = 0; i < tokens.Length; i++)
			{
				res[i] = tokens[i].ToString();
			}
			return res;
		}

		public static string[] GetNames (Variable[] vars)
		{
			if (vars == null) return null;

			var res = new string[vars.Length];
			for (int i = 0; i < vars.Length; i++)
			{
				res[i] = vars[i].Name;
			}
			return res;
		}
		#endregion

		#region - Modifications. -
		/// <summary>
		/// [molecule]
		/// 
		/// Removes comments from the given lines.
		/// </summary>
		/// <param name="lines">Text to remove comments from.</param>
		/// <param name="commBegin">The line from which comments should begin.</param>
		/// <returns>Source text without comments.</returns>
		public static string[] RemoveComments(string[] lines, string commBegin)
		{
			var res = new List<string>();
			foreach (var line in lines)
			{
				var str = line;
				var idx = str.IndexOf(commBegin);
				if (idx < 0)
				{
					res.Add(str);
				}
				else if (idx == 0)
				{
					continue;
				}
				else
				{	// idx > 0
					res.Add(str.Substring(0, idx-1));
				}
			}

			return res.ToArray();
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Splits input string into substrings, but preserves text in quotes from splitting.
		/// No exape sequences like '\'' and '\"' should be in the input string.
		/// </summary>
		/// <param name="str">Input string.</param>
		/// <param name="seps">Split separators.</param>
		/// <returns>Array of splitted lines.</returns>
		public static string[] Split (string str, char[] seps)
		{
			var res = new List<string>();
			//var spaces = new[] {' ', '\t', '\n'};
			var quotes = new[] {'\'', '\"'};
			var count = 0;
			do
			{
				var idx = str.IndexOfAny(quotes, count);
				if (idx < 0)
				{
					var split = str.Substring(count).Trim().Split(seps);
					res.AddRange(split);
					break;
				}

				// else quotes are found.
				if (count != idx)
				{
					var split = str.Substring(count, idx - count).Trim().Split(seps);
					res.AddRange(split);
				}
				var idx2 = str.IndexOfAny(quotes, idx + 1);
				var sub = str.Substring(idx, idx2 - idx+1);
				res.Add(sub);
				count = idx2 + 1;
			} while (true);

			return res.ToArray();
		}
		#endregion

		#region - Search. -
		/// <summary>
		/// [molecule]
		/// 
		/// Looks for the token with specified type starting from the given position and returns
		/// its index. If no token is found then -1 is returned.
		/// </summary>
		/// <param name="tokens"></param>
		/// <param name="index"></param>
		/// <param name="type"></param>
		/// <returns></returns>
		public static int FirstTokenIndex (Token[] tokens, int index, TokenType type)
		{
			for (int i = index; i < tokens.Length; i++)
			{
				if (tokens[i].TokenType == type)
				{
					return i;
				}
			}
			return -1;
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Looks for the token with specified contents starting from the given position and returns
		/// its index. If no token is found then -1 is returned.
		/// </summary>
		/// <param name="tokens"></param>
		/// <param name="index"></param>
		/// <param name="contents"></param>
		/// <param name="ignoreCase"></param>
		/// <returns></returns>
		public static int FirstTokenIndex(Token[] tokens, int index, string contents, bool ignoreCase)
		{
			for (int i = index; i < tokens.Length; i++)
			{
				if (string.Compare(tokens[i].Text, contents, ignoreCase) == 0)
				{
					return i;
				}
			}
			return -1;
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Returns index of the first closing bracket token with specified level.
		/// </summary>
		/// <param name="tokens">Array of tokens.</param>
		/// <param name="level">Closing bracket level.</param>
		/// <returns>Token index.</returns>
		public static int IndexOfClosingBracket (Token[] tokens, int level)
		{
			for (int i = 0; i < tokens.Length; i++)
			{
				if (tokens[i].TokenType == TokenType.ClosingBracket
					&& (int)tokens[i].Tag == level)
				{
					return i;
				}
			}
			return -1;
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Looks for all 'high-level' nodes with specified contents
		/// starting from the given node. 'High-level' means that the
		/// search through some branch is stopped whenever the first
		/// required node is found ('no recursion on success').
		/// </summary>
		/// <param name="cont"></param>
		/// <param name="node"></param>
		/// <returns></returns>
		public static List<ParseTreeNode> FindNodes (string cont, ParseTreeNode node)
		{
			var res = new List<ParseTreeNode>();

			if (node.Contents == cont)
			{
				res.Add(node);
				return res;
			}

			foreach(var childNode in node.ChildNodes)
			{
				var tempRes = FindNodes(cont, childNode);
				if (tempRes.Count > 0)
				{
					res.AddRange(tempRes);
				}
			}

			return res;
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Looks for the index of the command associated the given label.
		/// </summary>
		/// <param name="cmds"></param>
		/// <param name="label"></param>
		/// <returns></returns>
		public static int FindIndexByLabel (List<Command> cmds, string label)
		{
			for (int i = 0; i < cmds.Count; i++)
			{
				var cmd = cmds[i];
				if (cmd.Label == label) return i;
			}
			return -1;
		}

		#endregion

		#region - Utility methods. -
		/// <summary>
		/// [molecule]
		/// 
		/// Defines whether given string represetnts default arithmetic or logical operator.
		/// </summary>
		/// <param name="str"></param>
		/// <returns></returns>
		public static bool IsMathOperator(string str)
		{
			if (str == BNFExpression.DEFAULT_MINUS_OPERATOR) {return true;}
			if (str == BNFExpression.DEFAULT_PLUS_OPERATOR) { return true; }
			if (str == BNFExpression.DEFAULT_DIVIDE_OPERATOR) { return true; }
			if (str == BNFExpression.DEFAULT_MULT_OPERATOR) { return true; }
			if (str == BNFExpression.DEFAULT_LESS_OPERATOR) { return true; }
			if (str == BNFExpression.DEFAULT_LESS_EQUAL_OPERATOR) { return true; }
			if (str == BNFExpression.DEFAULT_UNEQUAL_OPERATOR) { return true; }
			if (str == BNFExpression.DEFAULT_EQUAL_OPERATOR) { return true; }
			if (str == BNFExpression.DEFAULT_MORE_OPERATOR) { return true; }
			if (str == BNFExpression.DEFAULT_MORE_EQUAL_OPERATOR) { return true; }
			if (str == BNFExpression.DEFAULT_AND_KEYWORD) { return true; }
			if (str == BNFExpression.DEFAULT_OR_KEYWORD) { return true; }

			return false;
		}
		#endregion

		#region - Info. -
		/// <summary>
		/// [molecule]
		/// 
		/// Defines type, which is common for both variables.
		/// Possible results:
		///		- float.
		///		- string.
		///		- DateTime.
		/// </summary>
		/// <param name="var1"></param>
		/// <param name="var2"></param>
		/// <returns></returns>
		public static Type GetCommonType (Variable var1, Variable var2)
		{
			// check if each of variables can be converted to DateTime.
			DateTime tempDate;
			if (DateTime.TryParse(var1.Value, out tempDate) && DateTime.TryParse(var2.Value, out tempDate))
			{
				return typeof (DateTime);
			}

			// check if each of variables can be converted to float.
			float tempNum;
			if (float.TryParse(var1.Value, out tempNum) && float.TryParse(var2.Value, out tempNum))
			{
				return typeof(float);
			}

			// both checks have failed hence the common type is [string].
            return typeof(string);
		}
		#endregion
	}

	[Serializable]
	public class ParseTree
	{
		public string Name;
		public List<ParseTreeNode> Nodes;
		public ScriptSyntaxis Syntaxis;
		public object Tag;

		public static ParseTree Load (string filename)
		{
			IFormatter formatter = new BinaryFormatter();
			Stream stream = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.None);
			var tree = (ParseTree)formatter.Deserialize(stream);
			stream.Close();

			return tree;
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Resets all IsExecuted fields to false.
		/// </summary>
		public void ResetExecutionStatus()
		{
			foreach (var node in Nodes)
			{
				node.ResetExecutionStatus();
			}
		}
	}


	public enum ParseTreeNodeType
	{
		Undefined,
		Root,
		Identifier,
		IfStatement,
		Condition,
		MathOperation,
		Assignment, 
		FunctionCall
	}

	#region - Parse tree node. -
	[Serializable]
	public class ParseTreeNode
	{
		protected bool isExecuted = false;

		public ParseTreeNodeType NodeType;
		public string Contents;
		public object Tag;
		public ParseTreeNode Parent;
		public List<ParseTreeNode> ChildNodes = new List<ParseTreeNode>();
		
		public Token Token;		// token 
		public BNFExpression Bnf;	// and BNF associated with the node.

		/// <summary>
		/// Indicates whether node has been executed or not.
		/// </summary>
		public bool IsExecuted 
		{ 
			get
			{
				return IsExecuted;
			} 
			set
			{
				foreach (var node in ChildNodes)
				{
					node.IsExecuted = value;
				}
				IsExecuted = value;
			} 
		}

		#region - Constructors. -
		public ParseTreeNode() { }
		public ParseTreeNode(ParseTreeNode node)
		{
			Assign(node);
		}
		public ParseTreeNode(Token token)
		{
			Contents = token.Text;
			Token = token;
		}
		#endregion

		#region - Public methods. -
		public ParseTreeNode Clone ()
		{
			return new ParseTreeNode(this);
		}

		public void Assign(ParseTreeNode node)
		{
			NodeType = node.NodeType;
			Contents = (string)node.Contents.Clone();
			Tag = node.Tag;
			Parent = node.Parent;
			Token = node.Token;
			Bnf = node.Bnf;
			ChildNodes = new List<ParseTreeNode>(node.ChildNodes);
		}

		/// <summary>
		/// Defines whether the node represents given BNF expression.
		/// </summary>
		/// <param name="bnfName"></param>
		/// <returns></returns>
		public bool IsBnfExpression(string bnfName)
		{
			if (Bnf != null && (Bnf.Name == bnfName || Bnf.Alias == bnfName)) return true;
			return false;
		} 

		public bool Equals (ParseTreeNode node)
		{
			return (NodeType == node.NodeType && Contents == node.Contents);
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Resets execution status.
		/// </summary>
		public void ResetExecutionStatus()
		{
			IsExecuted = false;
		}
		#endregion
	}
	#endregion
}
